package top.mcwebsite.playandroid.lib.markdown.render

import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.foundation.text.appendInlineContent
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.geometry.isUnspecified
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.sp
import androidx.compose.ui.unit.toSize
import org.intellij.markdown.MarkdownElementTypes
import org.intellij.markdown.MarkdownTokenTypes
import org.intellij.markdown.ast.ASTNode
import org.intellij.markdown.ast.CompositeASTNode
import org.intellij.markdown.ast.LeafASTNode
import top.mcwebsite.playandroid.lib.markdown.utils.parseLink
import java.util.UUID

@Composable
fun RichText(
    markdownState: MarkdownState,
    astNode: ASTNode?,
    style: TextStyle = TextStyle(),
    modifier: Modifier
) {
    val uriHandler = LocalUriHandler.current
    val (annotatedString, inlineMap) = remember(astNode) {
        if (astNode != null) {
            buildAnnotatedWithAstNode(
                markdownState = markdownState,
                astNode = astNode,
                style = style
            )
        } else {
            buildAnnotatedString { append("") } to emptyMap()
        }
    }
    val layoutResult = remember {
        mutableStateOf<TextLayoutResult?>(null)
    }
    val size = rememberMarkdownImageState()
    val pressIndicator =
        if (annotatedString.hasStringAnnotations("LINK", 0, annotatedString.length)) {
            Modifier.pointerInput(Unit) {
                detectTapGestures { pos ->
                    layoutResult.value?.let { layoutResult ->
                        val position = layoutResult.getOffsetForPosition(pos)
                        annotatedString.getStringAnnotations("LINK", position, position)
                            .firstOrNull()?.let { stringAnnotation ->
                                uriHandler.openUri(stringAnnotation.item)
                            }
                    }
                }
            }
        } else Modifier
    Text(
        text = annotatedString,
        inlineContent = inlineMap.mapValues {
            when (val inline = it.value) {
//        MarkdownElementTypes.CODE_SPAN -> BuildCodeSpanInlineContent(astNode, markdownState, spanStyle)
                is MarkdownImage -> InlineTextContent(
                    placeholder = Placeholder(
                        width = size.imageSize.width.sp,
                        height = size.imageSize.height.sp,
                        PlaceholderVerticalAlign.Center
                    ),
                    children = {
                        Image(image = inline) { imageSize ->
                            size.setImageSize(imageSize)
                        }
                    }
                )

                else -> throw IllegalArgumentException()
            }

        },
        onTextLayout = {
            layoutResult.value = it
        },
        modifier = modifier
            .then(pressIndicator)
            .onPlaced {
                it.parentCoordinates?.also { coordinates ->
                    size.setContainerSize(coordinates.size)
                }
            },
        style = style,
    )
}

fun buildAnnotatedWithAstNode(
    markdownState: MarkdownState,
    astNode: ASTNode,
    style: TextStyle
): Pair<AnnotatedString, Map<String, Inline>> {
    val builder = AnnotatedString.Builder()
    val spanStyle = style.toSpanStyle()
    val inlineMap = mutableMapOf<String, Inline>()
    astNode.children.forEach {
        if (it is LeafASTNode) {
            parseLeafNode(builder, markdownState, it, spanStyle)
        }
        if (it is CompositeASTNode) {
            parseCompositeASTNode(builder, inlineMap, markdownState, it, spanStyle)
        }
    }

    return builder.toAnnotatedString() to inlineMap
}

@OptIn(ExperimentalTextApi::class)
fun parseCompositeASTNode(
    builder: AnnotatedString.Builder,
    inlineMaps: MutableMap<String, Inline>,
    markdownState: MarkdownState,
    node: ASTNode,
    spanStyle: SpanStyle,
) {
    when (node.type) {
        MarkdownElementTypes.STRONG -> {
            node.children.forEach {
                parseLeafNode(builder, markdownState, it, spanStyle.copy(fontWeight = FontWeight.Bold))
            }
        }

        MarkdownElementTypes.EMPH -> {
            node.children.forEach {
                parseLeafNode(builder, markdownState, it, spanStyle.copy(fontStyle = FontStyle.Italic))
            }
        }

        MarkdownElementTypes.CODE_SPAN -> {
            node.children.forEach {
                parseLeafNode(
                    builder,
                    markdownState,
                    it,
                    spanStyle.copy(
                        fontFamily = FontFamily.Monospace,
                        fontWeight = FontWeight.Bold,
                        background = Color.Gray.copy(alpha = 0.5f)
                    )
                )
            }
        }

        MarkdownElementTypes.IMAGE -> {
            val id = UUID.randomUUID().toString()
            inlineMaps[id] = parseImage(node, markdownState.text)
            builder.appendInlineContent(id)
        }

        MarkdownElementTypes.INLINE_LINK -> {
            val (linkText, linkDestination) = parseLink(node, markdownState.text)
            builder.withStyle(spanStyle.copy(color = Color.Blue)) {
                append(linkText)
                addStringAnnotation("LINK", linkDestination, length - linkText.length, length)
            }
        }
    }
}

fun parseLeafNode(
    builder: AnnotatedString.Builder,
    markdownState: MarkdownState,
    node: ASTNode,
    spanStyle: SpanStyle
) {
    when (node.type) {
        MarkdownTokenTypes.TEXT -> {
            builder.withStyle(spanStyle) {
                append(markdownState.text.substring(node.startOffset, node.endOffset))
            }
        }

        MarkdownTokenTypes.EMPH -> {
            if (!(node.parent?.type == MarkdownElementTypes.STRONG || node.parent?.type == MarkdownElementTypes.EMPH)) {

                builder.withStyle(spanStyle) {
                    append("*")
                }
            }
        }

        MarkdownTokenTypes.WHITE_SPACE -> {
            builder.append(" ")
        }

        MarkdownTokenTypes.BACKTICK -> {
//            if (node.parent?.type == MarkdownElementTypes.CODE_SPAN) {
//                builder.append(" ")
//            }
        }

        MarkdownTokenTypes.EOL -> {
            builder.appendLine()
        }
    }
}

interface Inline {
}

@Composable
fun BuildInlineContent(inline: Inline, markdownState: MarkdownState, spanStyle: SpanStyle): InlineTextContent =
    when (inline) {
//        MarkdownElementTypes.CODE_SPAN -> BuildCodeSpanInlineContent(astNode, markdownState, spanStyle)
        is MarkdownImage -> ImageInlineContent(inline, markdownState = markdownState, inline.size)
        else -> throw IllegalArgumentException()
    }

class MarkdownImageState(private val density: Density) {

    private var parentSize by mutableStateOf(Size.Unspecified)

    private var intrinsicImageSize by mutableStateOf(Size.Unspecified)

    val imageSize by derivedStateOf {
        with(density) {
            if (parentSize.isUnspecified) {
                Size(340f, 340f)
            } else if (intrinsicImageSize.isUnspecified) {
                Size(parentSize.width.toSp().value, 180f)
            } else {
                val width = minOf(intrinsicImageSize.width, parentSize.width)

                val height = if (intrinsicImageSize.width < parentSize.width) {
                    intrinsicImageSize.height
                } else {
                    (intrinsicImageSize.height * parentSize.width) / intrinsicImageSize.width
                }
                Size(width.toSp().value, height.toSp().value)
            }
        }
    }

    fun setContainerSize(intSize: IntSize) {
        parentSize = intSize.toSize()
    }

    fun setImageSize(size: Size) {
        intrinsicImageSize = size
    }
}

@Composable
internal fun rememberMarkdownImageState(): MarkdownImageState {
    val density = LocalDensity.current
    return remember(density) {
        MarkdownImageState(density)
    }
}